using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;

namespace Life
{
    /// <summary>
    /// Manages the game state and generation (lifecycle iteration) progression
    /// Generation == lifecycle iteration
    /// </summary>
    class GameManager : IGameManager
    {
        private int _boardWidth = Constants.DefaultBoardWidth;
        private int _boardHeight = Constants.DefaultBoardHeight;

        // 0 == dead, 1 == live
        // TODO: 0s and 1s should not be hardcoded everywhere
        private int[,] _board;

        private GameTimer _generationTimer;
        private int _generationIntervalMilliseconds = Constants.DefaultGenerationIntervalMilliseconds;
        private bool _generationStarted;
        private int _generation;

        // Used to determine whether Random or Clear button is shown
        private int liveCount;

        private bool isInitialized;

        public GameManager(Game game)
        {
            // Start generation timer
            _generationTimer = new GameTimer();
            _generationTimer.UpdateInterval = new TimeSpan(0, 0, 0, 0, _generationIntervalMilliseconds);
            _generationTimer.Update += new EventHandler<GameTimerEventArgs>(GenerationTimerUpdate);
            _generationStarted = false;

            _board = new int[_boardWidth, _boardHeight];

            liveCount = 0;
            isInitialized = false;

            _generation = 0;
        }

        public bool IsPaused
        {
            get { return _generationStarted == false; }
        }

        public bool IsClear
        {
            get { return liveCount == 0; }
        }

        public int Generation
        {
            get { return _generation; }
        }

        public int BoardWidth
        {
            get { return _boardWidth; }
        }
        public int BoardHeight
        {
            get { return _boardHeight; }
        }

        public int GenerationIntervalMilliseconds
        {
            get 
            { 
                return _generationIntervalMilliseconds; 
            }
            set 
            { 
                // Ensure generation interval is within bounds
                if (value > Constants.MaxGrowthIntervalMilliseconds)
                    _generationIntervalMilliseconds = Constants.MaxGrowthIntervalMilliseconds;
                else if (value < Constants.MinGrowthIntervalMilliseconds)
                    _generationIntervalMilliseconds = Constants.MinGrowthIntervalMilliseconds;
                else
                    _generationIntervalMilliseconds = value;

                _generationTimer.UpdateInterval = new TimeSpan(0, 0, 0, 0, _generationIntervalMilliseconds);
            }
        }

        public void Initialize()
        {
            if (!isInitialized)
            {
                // Load current game
                SaveGameData saveGameData = FileManager.Load<SaveGameData>(Constants.CurrentSaveFilename);
                if (saveGameData != null)
                {
                    Update(saveGameData);
                }
                else
                {
                    _board = new int[_boardWidth, _boardHeight];
                    liveCount = 0;
                }

                CreateStockSavedGames();

                isInitialized = true;
            }
        }

        private void CreateStockSavedGames()
        {
            if (_boardWidth >= 6 && _boardHeight >= 6)
            {
                // TODO: Don't overwrite these
                // TODO: Don't recreate these if the user has deleted them

                // Beacon
                SaveGameData beacon = new SaveGameData();
                beacon.BoardHeight = _boardHeight;
                beacon.BoardWidth = _boardWidth;
                beacon.Version = 1;
                beacon.LiveCells = new List<Point>();
                beacon.LiveCells.Add(new Point(beacon.BoardWidth / 2 - 2, beacon.BoardHeight / 2 - 2));
                beacon.LiveCells.Add(new Point(beacon.BoardWidth / 2 - 1, beacon.BoardHeight / 2 - 2));
                beacon.LiveCells.Add(new Point(beacon.BoardWidth / 2 - 2, beacon.BoardHeight / 2 - 1));
                beacon.LiveCells.Add(new Point(beacon.BoardWidth / 2 - 1, beacon.BoardHeight / 2 - 1));

                beacon.LiveCells.Add(new Point(beacon.BoardWidth / 2, beacon.BoardHeight / 2));
                beacon.LiveCells.Add(new Point(beacon.BoardWidth / 2 + 1, beacon.BoardHeight / 2));
                beacon.LiveCells.Add(new Point(beacon.BoardWidth / 2, beacon.BoardHeight / 2 + 1));
                beacon.LiveCells.Add(new Point(beacon.BoardWidth / 2 + 1, beacon.BoardHeight / 2 + 1));
                FileManager.Save<SaveGameData>("BeaconSample.sav", beacon);

                // Beehive
                SaveGameData beehive = new SaveGameData();
                beehive.BoardHeight = _boardHeight;
                beehive.BoardWidth = _boardWidth;
                beehive.Version = 1;
                beehive.LiveCells = new List<Point>();
                beehive.LiveCells.Add(new Point(beehive.BoardWidth / 2 - 1, beehive.BoardHeight / 2 - 1));
                beehive.LiveCells.Add(new Point(beehive.BoardWidth / 2, beehive.BoardHeight / 2 - 1));
                beehive.LiveCells.Add(new Point(beehive.BoardWidth / 2 - 2, beehive.BoardHeight / 2));
                beehive.LiveCells.Add(new Point(beehive.BoardWidth / 2 + 1, beehive.BoardHeight / 2));
                beehive.LiveCells.Add(new Point(beehive.BoardWidth / 2 - 1, beehive.BoardHeight / 2 + 1));
                beehive.LiveCells.Add(new Point(beehive.BoardWidth / 2, beehive.BoardHeight / 2 + 1));
                FileManager.Save<SaveGameData>("BeehiveSample.sav", beehive);

                // Blinker
                SaveGameData blinker = new SaveGameData();
                blinker.BoardHeight = _boardHeight;
                blinker.BoardWidth = _boardWidth;
                blinker.Version = 1;
                blinker.LiveCells = new List<Point>();
                blinker.LiveCells.Add(new Point(blinker.BoardWidth / 2 - 1, blinker.BoardHeight / 2 - 1));
                blinker.LiveCells.Add(new Point(blinker.BoardWidth / 2 - 1, blinker.BoardHeight / 2));
                blinker.LiveCells.Add(new Point(blinker.BoardWidth / 2 - 1, blinker.BoardHeight / 2 + 1));
                FileManager.Save<SaveGameData>("BlinkerSample.sav", blinker);

                // Block
                SaveGameData block = new SaveGameData();
                block.BoardHeight = _boardHeight;
                block.BoardWidth = _boardWidth;
                block.Version = 1;
                block.LiveCells = new List<Point>();
                block.LiveCells.Add(new Point(block.BoardWidth / 2 - 1, block.BoardHeight / 2 - 1));
                block.LiveCells.Add(new Point(block.BoardWidth / 2, block.BoardHeight / 2 - 1));
                block.LiveCells.Add(new Point(block.BoardWidth / 2 - 1, block.BoardHeight / 2));
                block.LiveCells.Add(new Point(block.BoardWidth / 2, block.BoardHeight / 2));
                FileManager.Save<SaveGameData>("BlockSample.sav", block);

                // Boat
                SaveGameData boat = new SaveGameData();
                boat.BoardHeight = _boardHeight;
                boat.BoardWidth = _boardWidth;
                boat.Version = 1;
                boat.LiveCells = new List<Point>();
                boat.LiveCells.Add(new Point(boat.BoardWidth / 2 - 1, boat.BoardHeight / 2 - 1));
                boat.LiveCells.Add(new Point(boat.BoardWidth / 2, boat.BoardHeight / 2 - 1));
                boat.LiveCells.Add(new Point(boat.BoardWidth / 2 - 1, boat.BoardHeight / 2));
                boat.LiveCells.Add(new Point(boat.BoardWidth / 2 + 1, boat.BoardHeight / 2));
                boat.LiveCells.Add(new Point(boat.BoardWidth / 2, boat.BoardHeight / 2 + 1));
                FileManager.Save<SaveGameData>("BoatSample.sav", boat);

                // Glider
                SaveGameData glider = new SaveGameData();
                glider.BoardHeight = _boardHeight;
                glider.BoardWidth = _boardWidth;
                glider.Version = 1;
                glider.LiveCells = new List<Point>();
                glider.LiveCells.Add(new Point(2, 1));
                glider.LiveCells.Add(new Point(3, 2));
                glider.LiveCells.Add(new Point(1, 3));
                glider.LiveCells.Add(new Point(2, 3));
                glider.LiveCells.Add(new Point(3, 3));
                FileManager.Save<SaveGameData>("GliderSample.sav", glider);

                // Loaf
                SaveGameData loaf = new SaveGameData();
                loaf.BoardHeight = _boardHeight;
                loaf.BoardWidth = _boardWidth;
                loaf.Version = 1;
                loaf.LiveCells = new List<Point>();
                loaf.LiveCells.Add(new Point(loaf.BoardWidth / 2 - 1, loaf.BoardHeight / 2 - 1));
                loaf.LiveCells.Add(new Point(loaf.BoardWidth / 2, loaf.BoardHeight / 2 - 1));
                loaf.LiveCells.Add(new Point(loaf.BoardWidth / 2 - 2, loaf.BoardHeight / 2));
                loaf.LiveCells.Add(new Point(loaf.BoardWidth / 2 + 1, loaf.BoardHeight / 2));
                loaf.LiveCells.Add(new Point(loaf.BoardWidth / 2 - 1, loaf.BoardHeight / 2 + 1));
                loaf.LiveCells.Add(new Point(loaf.BoardWidth / 2 + 1, loaf.BoardHeight / 2 + 1));
                loaf.LiveCells.Add(new Point(loaf.BoardWidth / 2, loaf.BoardHeight / 2 + 2));
                FileManager.Save<SaveGameData>("LoafSample.sav", loaf);

                // Toad
                SaveGameData toad = new SaveGameData();
                toad.BoardHeight = _boardHeight;
                toad.BoardWidth = _boardWidth;
                toad.Version = 1;
                toad.LiveCells = new List<Point>();
                toad.LiveCells.Add(new Point(toad.BoardWidth / 2 - 1, toad.BoardHeight / 2 - 1));
                toad.LiveCells.Add(new Point(toad.BoardWidth / 2, toad.BoardHeight / 2 - 1));
                toad.LiveCells.Add(new Point(toad.BoardWidth / 2 - 1, toad.BoardHeight / 2));
                toad.LiveCells.Add(new Point(toad.BoardWidth / 2, toad.BoardHeight / 2));
                toad.LiveCells.Add(new Point(toad.BoardWidth / 2 - 2, toad.BoardHeight / 2));
                toad.LiveCells.Add(new Point(toad.BoardWidth / 2 + 1, toad.BoardHeight / 2 - 1));
                FileManager.Save<SaveGameData>("ToadSample.sav", toad);
            }
        }

        // Stop generation, save current generation
        public void Deactivate()
        {
            Stop();
            FileManager.Save<SaveGameData>(Constants.CurrentSaveFilename, GetSaveGameData());
        }

        public void Start()
        {
            _generationTimer.Start();
            _generationStarted = true;
        }

        public void Stop()
        {
            _generationTimer.Stop();
            _generationStarted = false;
        }

        private void GenerationTimerUpdate(object sender, GameTimerEventArgs e)
        {
            NextGeneration();
        }

        public SaveGameData GetSaveGameData()
        {
            SaveGameData sgd = new SaveGameData();
            sgd.Version = 1;
            sgd.BoardWidth = _boardWidth;
            sgd.BoardHeight = _boardHeight;

            // Add live cells
            sgd.LiveCells = new List<Point>();
            for (int i = 0; i < _boardWidth; i++)
                for (int j = 0; j < _boardHeight; j++)
                    if (_board[i, j] == 1)
                        sgd.LiveCells.Add(new Point(i, j));

            return sgd;
        }

        public bool IsLive(int x, int y)
        {
            return (_board[x, y] > 0);
        }

        private void NextGeneration()
        {
            // Copy board
            int[,] copyboard = new int[_boardWidth, _boardHeight];
            for (int i = 0; i < _boardWidth; i++)
                for (int j = 0; j < _boardHeight; j++)
                    copyboard[i, j] = _board[i, j];

            // Rules:
            // 1. Any live cell with fewer than two live neighbours dies, as if caused by under-population.
            // 2. Any live cell with two or three live neighbours lives on to the next generation.
            // 3. Any live cell with more than three live neighbours dies, as if by overcrowding.
            // 4. Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.

            for (int i = 0; i < _boardWidth; i++)
            {
                for (int j = 0; j < _boardHeight; j++)
                {
                    // Find number of neighbors
                    int neighbors = 0;

                    // Check 8 neighbors
                    if (j > 0 && i > 0 && copyboard[i - 1, j - 1] > 0) // up left
                        neighbors++;
                    if (j > 0 && copyboard[i, j - 1] > 0) // up
                        neighbors++;
                    if (i < _boardWidth - 1 && j > 0 && copyboard[i + 1, j - 1] > 0) // up right
                        neighbors++;
                    if (i < _boardWidth - 1 && copyboard[i + 1, j] > 0) // right
                        neighbors++;
                    if (i < _boardWidth - 1 && j < _boardHeight - 1 && copyboard[i + 1, j + 1] > 0) // down right
                        neighbors++;
                    if (j < _boardHeight - 1 && copyboard[i, j + 1] > 0) // down
                        neighbors++;
                    if (i > 0 && j < _boardHeight - 1 && copyboard[i - 1, j + 1] > 0) // down left
                        neighbors++;
                    if (i > 0 && copyboard[i - 1, j] > 0) // left
                        neighbors++;

                    if (copyboard[i, j] == 0)
                    {
                        if (neighbors == 3)
                        {
                            _board[i, j] = 1;
                            liveCount++;
                        }
                    }
                    else
                    {
                        if (neighbors < 2 || neighbors > 3)
                        {
                            _board[i, j] = 0;
                            liveCount--;
                        }
                    }
                }
            }

            _generation++;
        }
        
        public void ToggleCell(int x, int y)
        {
            if (0 <= x && x < _boardWidth &&
                0 <= y && y < _boardHeight)
            {
                if (_board[x, y] == 0)
                {
                    _board[x, y] = 1;
                    liveCount++;
                }
                else
                {
                    _board[x, y] = 0;
                    liveCount--;
                }
            }
        }

        public void Update(SaveGameData saveGameData)
        {
            if (saveGameData.Version > 0 && saveGameData.BoardWidth > 0 && saveGameData.BoardHeight > 0)
            {
                _boardWidth = saveGameData.BoardWidth;
                _boardHeight = saveGameData.BoardHeight;

                _board = new int[_boardWidth, _boardHeight];
                liveCount = 0;

                if (saveGameData.LiveCells != null)
                {
                    foreach (Point p in saveGameData.LiveCells)
                    {
                        if (p.X >= 0 && p.X < _boardWidth &&
                            p.Y >= 0 && p.Y < _boardHeight)
                        {
                            _board[p.X, p.Y] = 1;
                            liveCount++;
                        }
                    }
                }

                _generation = 0;
            }
            else
            {
                Clear();
            }
        }

        public void Clear()
        {
            _board = new int[_boardWidth, _boardHeight];
            liveCount = 0;
            _generation = 0;
        }

        public void Randomize()
        {
            Random r = new Random();

            for (int i = 0; i < _boardWidth; i++)
            {
                for (int j = 0; j < _boardHeight; j++)
                {
                    if (r.Next(1, 3) == 1)
                    {
                        _board[i, j] = 1;
                        liveCount++;
                    }
                    else
                    {
                        _board[i, j] = 0;
                    }
                }
            }

            _generation = 0;
        }
    }
}
